#include <func.h>

#define PORT "9527"
#define BACKLOG 10
#define MAXLINE 5
#define MAXEVENTS 1024

int tcp_listen(const char* port)
{
    // 服务器监听 port 端口
    struct addrinfo hints;
    bzero(&hints, sizeof(hints));
    hints.ai_flags = AI_PASSIVE;    // 设置通配符地址
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;    // TCP

    struct addrinfo *result, *p;
    if (getaddrinfo(NULL, port, &hints, &result) == -1) {
        return -1;
    }

    // 遍历地址信息，绑定端口
    int sockfd;
    for (p = result; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
            continue;
        }
        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            continue;
        }
        if (listen(sockfd, BACKLOG) == -1) {
            close(sockfd);
            continue;
        }
        // socket, bind, listen 都成功了
        break;
    }

    // 释放地址信息
    freeaddrinfo(result);
    
    if (p == NULL) {
        return -1;
    }
    return sockfd;
}

void* getipaddr(struct sockaddr* addr)
{
    if (addr->sa_family == AF_INET) {
        return &((struct sockaddr_in*)addr)->sin_addr;
    }
    return &((struct sockaddr_in6*)addr)->sin6_addr;
}

// 监听fd的读事件
int epoll_add(int epfd, int fd)
{
    // 添加 listenfd 文件描述符
    struct epoll_event ev;
    ev.events = EPOLLIN;    // 监听读事件
    ev.data.fd = fd;  // 用户设置的数据
    return epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
}

int epoll_del(int epfd, int fd)
{
    return epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
}

int main(int argc, char* argv[])
{
    // 监听一个众所周知的端口
    int listenfd = tcp_listen(PORT);
    if (listenfd == -1) {
        error(1, errno, "tcp_listen()");
    }
    printf("INFO: listen on port %s\n", PORT);

    // I/O多路复用
    // 创建 epoll 实例
    int epfd = epoll_create(1);
    if (epfd == -1) {
        error(1, errno, "epoll_create()");
    }

    
    if (epoll_add(epfd, listenfd) == -1) {
        error(1, errno, "epoll_add()");
    }

    struct epoll_event revents[MAXEVENTS];

    for(;;) {
        int n = epoll_wait(epfd, revents, MAXEVENTS, -1);    // -1: 无限期阻塞
        if (n > 0) {
            for(int i = 0; i < n; i++) {
                int srcfd = revents[i].data.fd;
                if (revents[i].events & EPOLLIN) {  // 读事件就绪
                    if (srcfd == listenfd) {
                        // 有连接到达
                        struct sockaddr_storage ss;
                        socklen_t addrlen = sizeof(ss);
                        int connfd = accept(listenfd, (struct sockaddr*)&ss, &addrlen);

                        char ipstr[50];
                        inet_ntop(ss.ss_family, getipaddr(&ss), ipstr, 50);
                        printf("INFO: new connection from %s on socket %d\n", ipstr, connfd);

                        // 添加 connfd 到 epoll 实例
                        if (epoll_add(epfd, connfd) == -1) {
                            fprintf(stderr, "epoll_add(): %s\n", strerror(errno));
                            close(connfd);
                        }

                    } else {
                        // 客户端有数据到达
                        char message[MAXLINE];
                        int nbytes = recv(srcfd, message, MAXLINE, 0);
                        switch (nbytes) {
                        case 0:
                            // 对端断开连接
                            printf("INFO: %d closed\n", srcfd);
                            close(srcfd);      // Say BYE!
                                            
                            // 删除srcfd
                            epoll_del(epfd, srcfd);
                            break;
                        case -1:
                            printf("ERROR: recv %d failed\n", srcfd);
                            close(srcfd);
                            epoll_del(epfd, srcfd);
                            break;
                        default:
                            printf("%.*s\n", nbytes, message);
                            break;
                        }
                    }
                }
            }
        }
    }
    return 0;
}

